Example 2 Clustering Geometric ObjectsΒΆ

In this example we will look at a few of the tools provided by the clifford package for (4,1) conformal geometric algebra (CGA) and see how we can use them in a practical setting to cluster geometric objects via the simple K-means clustering algorithm provided in clifford.tools

As before the first step in using the package for CGA is to generate and import the algebra:

In [1]:
from clifford.g3c import *
print('e1*e1 ', e1*e1)
print('e2*e2 ', e2*e2)
print('e3*e3 ', e3*e3)
print('e4*e4 ', e4*e4)
print('e5*e5 ', e5*e5)
e1*e1  1.0
e2*e2  1.0
e3*e3  1.0
e4*e4  1.0
e5*e5  -1.0

The tools submodule of the clifford package contains a wide array of algorithms and tools that can be useful for manipulating objects in CGA. In this case we will be generating a large number of objects and then segmenting them into clusters.

We first need an algorithm for generating a cluster of objects in space. We will construct this cluster by generating a random object and then repeatedly disturbing this object by some small fixed amount and storing the result:

In [2]:
from clifford.tools.g3c import *
import numpy as np

def generate_random_object_cluster(n_objects, object_generator, max_cluster_trans=1.0, max_cluster_rot=np.pi/8):
    """ Creates a cluster of random objects """
    ref_obj = object_generator()
    cluster_objects = []
    for i in range(n_objects):
        r = random_rotation_translation_rotor(maximum_translation=max_cluster_trans, maximum_angle=max_cluster_rot)
        new_obj = apply_rotor(ref_obj, r)
        cluster_objects.append(new_obj)
    return cluster_objects

We can use this function to create a cluster and then we can visualise this cluster with GAOnline using the built in tools in clifford.

In [3]:
from clifford.tools.g3c.GAOnline import *
clustered_circles = generate_random_object_cluster(10, random_circle)
sc = GAScene()
for c in clustered_circles:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.01217^e123) + (0.28814^e124) + (0.35382^e125) - (0.1613^e134) - (0.1924^e135) - (0.13415^e145) + (0.61359^e234) + (0.71684^e235) + (0.86703^e245) - (0.1997^e345),rgb(255,0,0));
DrawCircle((0.01115^e123) + (0.147^e124) + (0.23182^e125) - (0.0291^e134) - (0.05752^e135) - (0.15342^e145) + (0.02949^e234) + (0.11861^e235) + (0.9508^e245) - (0.15743^e345),rgb(255,0,0));
DrawCircle((0.00839^e123) + (0.06117^e124) + (0.14735^e125) + (0.01245^e134) + (0.01533^e135) - (0.10695^e145) - (0.02918^e234) + (0.06305^e235) + (0.97256^e245) + (0.14693^e345),rgb(255,0,0));
DrawCircle((0.02093^e123) - (0.07401^e124) + (0.03312^e125) - (0.0182^e134) - (0.00909^e135) + (0.06093^e145) - (0.24376^e234) - (0.17755^e235) + (1.01336^e245) + (0.04847^e345),rgb(255,0,0));
DrawCircle(-(0.03095^e123) + (0.14983^e124) + (0.25337^e125) + (0.06371^e134) + (0.06308^e135) + (0.21622^e145) + (0.37665^e234) + (0.44892^e235) + (0.91015^e245) - (0.15652^e345),rgb(255,0,0));
DrawCircle((0.01605^e123) + (0.02878^e124) + (0.11928^e125) - (0.02146^e134) - (0.02103^e135) + (0.12175^e145) - (0.14735^e234) - (0.05931^e235) + (0.98859^e245) - (0.11376^e345),rgb(255,0,0));
DrawCircle(-(0.00186^e123) + (0.09704^e124) + (0.18605^e125) + (0.00133^e134) + (0.00244^e135) + (0.00514^e145) + (0.11798^e234) + (0.20753^e235) + (0.97241^e245) + (0.00704^e345),rgb(255,0,0));
DrawCircle(-(0.04426^e123) - (0.1844^e124) - (0.11101^e125) - (0.04304^e134) - (0.05326^e135) - (0.11395^e145) + (0.32538^e234) + (0.42762^e235) + (0.96545^e245) + (0.02429^e345),rgb(255,0,0));
DrawCircle(-(0.00152^e123) + (0.05481^e124) + (0.1433^e125) + (0.00165^e134) + (0.00465^e135) - (0.01193^e145) + (0.07259^e234) + (0.16261^e235) + (0.97933^e245) + (0.04533^e345),rgb(255,0,0));
DrawCircle((0.00319^e123) + (0.05383^e124) + (0.15563^e125) - (0.00325^e134) + (0.00227^e135) + (0.19675^e145) + (0.00925^e234) + (0.08374^e235) + (0.96157^e245) - (0.09182^e345),rgb(255,0,0));

This cluster generation function appears in clifford tools by default and it can be imported as follows:

In [4]:
from clifford.tools.g3c import generate_random_object_cluster

Now that we can generate individual clusters we would like to generate many:

In [5]:
def generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster ):
    object_clusters = []
    for i in range(n_clusters):
        cluster_objects = generate_random_object_cluster(n_objects_per_cluster, object_generator,
                                                         max_cluster_trans=0.5, max_cluster_rot=np.pi / 16)
        object_clusters.append(cluster_objects)
    all_objects = [item for sublist in object_clusters for item in sublist]
    return all_objects, object_clusters

Again this function appears by default in clifford tools and we can easily visualise the result:

In [6]:
from clifford.tools.g3c import generate_n_clusters

all_objects, object_clusters = generate_n_clusters(random_circle, 2, 5)
sc = GAScene()
for c in all_objects:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.08001^e123) + (0.72143^e124) + (0.70736^e125) + (0.68096^e134) + (0.62111^e135) + (0.41994^e145) + (0.68211^e234) + (0.56662^e235) + (0.92137^e245) + (0.47263^e345),rgb(255,0,0));
DrawCircle(-(0.09446^e123) + (0.97013^e124) + (0.93952^e125) + (0.667^e134) + (0.60608^e135) + (0.4095^e145) + (0.57772^e234) + (0.46605^e235) + (0.9596^e245) + (0.4159^e345),rgb(255,0,0));
DrawCircle(-(0.08088^e123) + (0.93401^e124) + (0.8998^e125) + (0.6765^e134) + (0.60823^e135) + (0.50229^e145) + (0.64397^e234) + (0.53773^e235) + (0.95455^e245) + (0.34506^e345),rgb(255,0,0));
DrawCircle(-(0.0723^e123) + (0.66846^e124) + (0.65309^e125) + (0.74136^e134) + (0.68244^e135) + (0.38712^e145) + (0.77748^e234) + (0.66168^e235) + (0.9053^e245) + (0.55378^e345),rgb(255,0,0));
DrawCircle(-(0.09985^e123) + (0.89056^e124) + (0.87691^e125) + (0.73739^e134) + (0.66742^e135) + (0.5232^e145) + (0.57785^e234) + (0.46814^e235) + (0.89951^e245) + (0.40531^e345),rgb(255,0,0));
DrawCircle(-(0.22181^e123) + (5.6368^e124) + (5.46656^e125) + (2.01846^e134) + (1.92162^e135) + (0.91183^e145) - (1.86016^e234) - (1.85783^e235) + (1.36844^e245) + (0.79093^e345),rgb(255,0,0));
DrawCircle(-(0.18321^e123) + (5.42385^e124) + (5.24973^e125) + (1.80913^e134) + (1.72102^e135) + (0.88912^e145) - (1.96588^e234) - (1.94899^e235) + (1.36831^e245) + (0.77866^e345),rgb(255,0,0));
DrawCircle(-(0.1693^e123) + (5.77333^e124) + (5.59309^e125) + (1.52528^e134) + (1.44986^e135) + (0.94803^e145) - (1.76921^e234) - (1.75554^e235) + (1.4174^e245) + (0.66499^e345),rgb(255,0,0));
DrawCircle(-(0.10803^e123) + (5.82613^e124) + (5.64903^e125) + (2.31672^e134) + (2.23318^e135) + (0.70753^e145) - (1.0858^e234) - (1.08139^e235) + (1.54205^e245) + (0.74505^e345),rgb(255,0,0));
DrawCircle(-(0.2199^e123) + (5.53419^e124) + (5.35611^e125) + (1.52424^e134) + (1.44312^e135) + (0.80723^e145) - (1.55681^e234) - (1.56501^e235) + (1.46727^e245) + (0.6312^e345),rgb(255,0,0));

Given that we can now generate multiple clusters of objects we can test algorithms for segmenting them.

The function run_n_clusters below generates a lot of objects distributed into n clusters and then attempts to segment the objects to recover the clusters.

In [7]:
from clifford.tools.g3c.object_clustering import n_clusters_objects
import time

def run_n_clusters( object_generator, n_clusters, n_objects_per_cluster, n_shotgunning):
    all_objects, object_clusters = generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster )
    [new_labels, centroids, start_labels, start_centroids] = n_clusters_objects(n_clusters, all_objects,
                                                                                initial_centroids=None,
                                                                                n_shotgunning=n_shotgunning,
                                                                                averaging_method='unweighted')
    return all_objects, new_labels, centroids

Lets try it!

In [8]:
from clifford.tools.g3c.object_clustering import visualise_n_clusters

object_generator = random_circle

n_clusters = 3
n_objects_per_cluster = 10
n_shotgunning = 60
all_objects, labels, centroids = run_n_clusters(object_generator, n_clusters,
                                                     n_objects_per_cluster, n_shotgunning)

sc = visualise_n_clusters(all_objects, centroids, labels, object_type='circle',
                     color_1=np.array([255, 0, 0]), color_2=np.array([0, 255, 0]))
print(sc)
DrawCircle((0.00117^e123) - (0.66604^e124) - (0.7134^e125) - (0.56988^e134) - (0.61089^e135) + (0.27633^e145) + (1.45519^e234) + (1.55846^e235) + (0.1097^e245) + (0.69759^e345),rgb(0, 255, 0));
DrawCircle((0.00271^e123) - (0.26555^e124) - (0.2866^e125) - (0.54073^e134) - (0.58527^e135) + (0.16419^e145) + (1.3858^e234) + (1.49609^e235) - (0.04306^e245) + (0.76915^e345),rgb(0, 255, 0));
DrawCircle(-(0.00416^e123) - (0.58334^e124) - (0.62697^e125) - (0.58425^e134) - (0.62583^e135) + (0.29599^e145) + (1.3996^e234) + (1.50427^e235) + (0.00069^e245) + (0.71086^e345),rgb(0, 255, 0));
DrawCircle(-(0.02853^e123) - (0.4501^e124) - (0.49401^e125) - (0.59608^e134) - (0.63486^e135) + (0.30568^e145) + (1.06274^e234) + (1.16837^e235) + (0.03085^e245) + (0.7626^e345),rgb(0, 255, 0));
DrawCircle(-(0.01275^e123) - (0.41454^e124) - (0.44747^e125) - (0.5472^e134) - (0.58383^e135) + (0.22252^e145) + (1.38672^e234) + (1.49701^e235) + (0.00439^e245) + (0.75018^e345),rgb(0, 255, 0));
DrawCircle((0.00698^e123) - (0.64536^e124) - (0.69732^e125) - (0.44058^e134) - (0.47962^e135) + (0.32969^e145) + (1.27647^e234) + (1.37828^e235) + (0.08839^e245) + (0.71244^e345),rgb(0, 255, 0));
DrawCircle(-(0.00535^e123) - (0.59166^e124) - (0.63663^e125) - (0.49087^e134) - (0.5255^e135) + (0.29543^e145) + (1.40067^e234) + (1.50729^e235) + (0.01937^e245) + (0.71546^e345),rgb(0, 255, 0));
DrawCircle((0.02546^e123) - (0.54678^e124) - (0.58802^e125) - (0.32696^e134) - (0.36452^e135) + (0.27696^e145) + (1.42789^e234) + (1.53503^e235) + (0.01203^e245) + (0.73047^e345),rgb(0, 255, 0));
DrawCircle(-(0.00091^e123) - (0.5556^e124) - (0.59518^e125) - (0.25214^e134) - (0.2697^e135) + (0.24411^e145) + (1.582^e234) + (1.69477^e235) + (0.04802^e245) + (0.71687^e345),rgb(0, 255, 0));
DrawCircle((0.01242^e123) - (0.59212^e124) - (0.63663^e125) - (0.41549^e134) - (0.45316^e135) + (0.30676^e145) + (1.40236^e234) + (1.50814^e235) - (0.01716^e245) + (0.71447^e345),rgb(0, 255, 0));
DrawCircle(-(0.01991^e123) - (0.2994^e124) - (0.21875^e125) + (0.00449^e134) - (0.00175^e135) - (0.07566^e145) + (0.05787^e234) + (0.10968^e235) + (1.01337^e245) - (0.02982^e345),rgb(127, 127, 0));
DrawCircle(-(0.02341^e123) - (0.21812^e124) - (0.13554^e125) - (0.02152^e134) - (0.01306^e135) + (0.00287^e145) + (0.15618^e234) + (0.20452^e235) + (1.00122^e245) + (0.10084^e345),rgb(127, 127, 0));
DrawCircle(-(0.00145^e123) - (0.38491^e124) - (0.30322^e125) + (0.02174^e134) + (0.01636^e135) - (0.20201^e145) - (0.21879^e234) - (0.16853^e235) + (1.01576^e245) + (0.05746^e345),rgb(127, 127, 0));
DrawCircle(-(0.01915^e123) - (0.21726^e124) - (0.12919^e125) + (0.00749^e134) + (0.00344^e135) - (0.0115^e145) + (0.12528^e234) + (0.16341^e235) + (1.00899^e245) - (0.04143^e345),rgb(127, 127, 0));
DrawCircle(-(0.02349^e123) - (0.38251^e124) - (0.3025^e125) + (0.01959^e134) + (0.01556^e135) + (0.0011^e145) + (0.04677^e234) + (0.09977^e235) + (1.02226^e245) - (0.05221^e345),rgb(127, 127, 0));
DrawCircle(-(0.03155^e123) - (0.26659^e124) - (0.1824^e125) - (0.00718^e134) - (0.01483^e135) - (0.08376^e145) + (0.23149^e234) + (0.27712^e235) + (1.00316^e245) - (0.0457^e345),rgb(127, 127, 0));
DrawCircle(-(0.02155^e123) - (0.37712^e124) - (0.29473^e125) + (0.00578^e134) + (0.00459^e135) + (0.00134^e145) + (0.04183^e234) + (0.09122^e235) + (1.02421^e245) - (0.01555^e345),rgb(127, 127, 0));
DrawCircle((0.02023^e123) - (0.09701^e124) - (0.01339^e125) - (0.00157^e134) + (0.00772^e135) - (0.03806^e145) - (0.29763^e234) - (0.25126^e235) + (1.00791^e245) + (0.13304^e345),rgb(127, 127, 0));
DrawCircle(-(0.01833^e123) - (0.34113^e124) - (0.26514^e125) + (0.0072^e134) - (0.00017^e135) - (0.10725^e145) - (0.01653^e234) + (0.04177^e235) + (1.01647^e245) - (0.01626^e345),rgb(127, 127, 0));
DrawCircle(-(0.02303^e123) - (0.48685^e124) - (0.40194^e125) + (0.01537^e134) + (0.00667^e135) - (0.12722^e145) + (0.02576^e234) + (0.06984^e235) + (1.02677^e245) - (0.03915^e345),rgb(127, 127, 0));
DrawCircle((0.02746^e123) + (0.09361^e124) + (0.03908^e125) + (0.05226^e134) + (0.04758^e135) + (0.08784^e145) + (0.27671^e234) + (0.38758^e235) + (0.9276^e245) + (0.25816^e345),rgb(255, 0, 0));
DrawCircle((0.01068^e123) + (0.06642^e124) + (0.03771^e125) + (0.01681^e134) + (0.01657^e135) + (0.04366^e145) + (0.08075^e234) + (0.20102^e235) + (0.96478^e245) + (0.1911^e345),rgb(255, 0, 0));
DrawCircle((0.04926^e123) + (0.19653^e124) + (0.15036^e125) + (0.07684^e134) + (0.07473^e135) + (0.0636^e145) + (0.49444^e234) + (0.60913^e235) + (0.92094^e245) + (0.20006^e345),rgb(255, 0, 0));
DrawCircle((0.02377^e123) + (0.15595^e124) + (0.11366^e125) + (0.03533^e134) + (0.02421^e135) - (0.01005^e145) + (0.11328^e234) + (0.22894^e235) + (0.96041^e245) + (0.22486^e345),rgb(255, 0, 0));
DrawCircle((0.03602^e123) + (0.21387^e124) + (0.16198^e125) + (0.05562^e134) + (0.05211^e135) + (0.05931^e145) + (0.20418^e234) + (0.31635^e235) + (0.9602^e245) + (0.19308^e345),rgb(255, 0, 0));
DrawCircle((0.06714^e123) + (0.44283^e124) + (0.39745^e125) + (0.09792^e134) + (0.0959^e135) + (0.0529^e145) + (0.30168^e234) + (0.41668^e235) + (0.96248^e245) + (0.17678^e345),rgb(255, 0, 0));
DrawCircle((0.00857^e123) + (0.04776^e124) + (0.00312^e125) + (0.01969^e134) + (0.03822^e135) + (0.20575^e145) + (0.06203^e234) + (0.17584^e235) + (0.9569^e245) + (0.12736^e345),rgb(255, 0, 0));
DrawCircle((0.01274^e123) + (0.10105^e124) + (0.06212^e125) + (0.02192^e134) + (0.03288^e135) + (0.15386^e145) + (0.01232^e234) + (0.12917^e235) + (0.96408^e245) + (0.1904^e345),rgb(255, 0, 0));
DrawCircle((0.01007^e123) - (0.03153^e124) - (0.06616^e125) + (0.03608^e134) + (0.04006^e135) + (0.11161^e145) + (0.36883^e234) + (0.48746^e235) + (0.89686^e245) + (0.27932^e345),rgb(255, 0, 0));
DrawCircle(-(0.00096^e123) - (0.1318^e124) - (0.17742^e125) + (0.04254^e134) + (0.05857^e135) + (0.17932^e145) + (0.30925^e234) + (0.42305^e235) + (0.92399^e245) + (0.12255^e345),rgb(255, 0, 0));
DrawCircle(-(0.00015^e123) - (0.53295^e124) - (0.57426^e125) - (0.47859^e134) - (0.51561^e135) + (0.27279^e145) + (1.38334^e234) + (1.49059^e235) + (0.02541^e245) + (0.73088^e345),rgb(0,0,0));
DrawCircle(-(0.01595^e123) - (0.30829^e124) - (0.22554^e125) - (0.00268^e134) - (0.00527^e135) - (0.0639^e145) + (0.01473^e234) + (0.06344^e235) + (1.01808^e245) + (0.0058^e345),rgb(0,0,0));
DrawCircle((0.02434^e123) + (0.1157^e124) + (0.07228^e125) + (0.04644^e134) + (0.04899^e135) + (0.09496^e145) + (0.22288^e234) + (0.33838^e235) + (0.94651^e245) + (0.19696^e345),rgb(0,0,0));